All imports to a module must themselves be modules.
Without modifiers, the imported module's public scope is added to the current module's private scope. The imported module is not made available to modules that import the current module.
The modifiers have the following meanings:
public
The imported module's public scope is added to the current module's public scope and made available to the current module's importers.
meta
The contents of the imported module are made available at the meta phase in the current module.
all
The imported module's private scope is added to the current module's private scope.
A module is a source file that has opted in to a distinction between public and private information.
Lean ensures that private information can change without affecting clients that import only its public information.
This discipline brings a number of benefits:
Much-improved average build times
Changes to files that affect only non-exported information (e.g. proofs, comments, and docstrings) will not trigger rebuilds outside of these files.
Even when dependent files have to be rebuilt, those files that cannot be affected (as determined by their Lean.Parser.Module.importimport annotations) can be skipped.
Control over API evolution
Library authors can trust that changes to non-exported information will not affect downstream users of their library.
If only a function's signature is exposed, then downstream users cannot rely on definitional equalities that involve its unfolding; this means that the library's author is free to adopt a more efficient algorithm without unintentionally breaking client code.
Avoiding accidental unfolding
Limiting the scope in which definitions can be unfolded allows for avoiding both reductions that should be replaced by application of more specific theorems as well as unproductive reductions that were not in fact necessary.
This improves the speed of proof elaboration.
Smaller executables
Separating compile-time and run-time code allows for more aggressive dead code elimination, guaranteeing that metaprograms such as tactics do not make it into the final binary.
Reduced memory usage
Excluding private information such as proofs from importing can improve Lean's memory use both while building and editing a project.
Porting mathlib4 to the module system has shown savings close to 50% from this even before imports are further minimized.
Modules contain two separate scopes: the public scope consists of information that is visible in modules that import a module, while the private scope consists of information that is generally visible only within the module.
Some examples of information that can be private or public include:
Names
Constants (such as definitions, inductive types, or constructors) may be private or public.
A public constant's type may only refer to public names.
Definitions
A public definition may be exposed or not.
If a public definition is not exposed, then it cannot be unfolded in contexts that only have access to the public scope.
Instead, clients must rely on the theorems about the definition that are provided in the public scope.
Each declaration has default visibility rules.
Generally speaking, all names are private by default, unless defined in a public section.
Even public names usually place the bodies of definitions in the private scope, and even proofs in exposed definitions are kept private.
The specific visibility rules for each declaration command are documented together with the declaration itself.
Private and Public Definitions
The module Greet.Create defines a function greeting.
Because there are no visibility modifiers, this function defaults to the private scope:
Although the definition of greeting is visible in the module Greet, it cannot be unfolded in a proof because the definition's body is in the private scope of Greet:
Nonetheless, the proof of the theorem incr_eq_plus1 can unfold its definition.
This is because proofs of theorems are in the private scope.
This is the case both for public and private theorems.
The option backward.privateInPublic can be used while transitioning from ordinary source files to modules.
When it is set to true, private definitions are exported, though their names are not accessible in importing modules.
However, references to them in the public part of their defining module are allowed.
Such references result in a warning unless the option backward.privateInPublic.warn is set to false.
These warnings can be used to locate and eventually eliminate these references, allowing backward.privateInPublic to be disabled.
Similarly, backward.proofsInPublic causes proofs created with Lean.Parser.Term.byby to be public, rather than private; this can enable Lean.Parser.Term.byby to fill in metavariables in its expected type.
Most use cases for backward.proofsInPublic also require that backward.privateInPublic is enabled.
(module system) Export private declarations, allowing for arbitrary access to them while code is being ported to the module system. Such accesses will generate warnings
unless backward.privateInPublic.warn is disabled.
(module system) Do not abstract proofs used in the public scope into auxiliary theorems. Enabling this option may lead to failures or, when backward.privateInPublic and its warn sub-option are enabled, additional warnings from private accesses.
Exporting Private Definitions
In the module L.Defs, the public definition of f refers to the private definition drop2 in its signature.
Because backward.privateInPublic is true, this is allowed, resulting in a warning:
L/Defs.leanmoduleset_optionbackward.privateInPublictruedefdrop2(xs:Listα):Listα:=xs.drop2publicdeff(xs:Listα)(transform:Listα→Listα:=Private declaration `drop2` accessed publicly; this is allowed only because the `backward.privateInPublic` option is enabled. Disable `backward.privateInPublic.warn` to silence this warning.drop2):Listα:=transformxs
Private declaration `drop2` accessed publicly; this is allowed only because the `backward.privateInPublic` option is enabled. Disable `backward.privateInPublic.warn` to silence this warning.
When the module is imported, references to f use drop2 as a default argument value; however, its name is inaccessible in the module L:
In the plain source file NotMod, the definition of two uses the content of the proof to fill out the numeric value in the definition by solving a metavariable:
Converting this file to a module results in an error, because the body of the definition is exposed in the public part but the proof is private and thus cannot change the public type:
Mod.leanmodulepublicsectionstructureHalf(n:Nat)whereval:Natok:val+val=nabbrevtwo:=Half.mk_<|tactic execution is stuck, goal contains metavariables?m.3+?m.3=?m.5byshow2+2=4rfl
tactic execution is stuck, goal contains metavariables?m.3+?m.3=?m.5
Setting the option backward.proofsInPublic causes the proof to be in the public part of the module so it can solve the metavariable:
The private scope of a module may be imported into another module using the Lean.Parser.Module.importall modifier.
By default, this is only allowed if the imported module and the current module are from the same Lake package, as its main purpose is to allow for separating definitions and proofs into separate modules for internal organization of a library.
The Lake package or library option allowImportAll can be set to allow other packages to access to the current package's private scopes via Lean.Parser.Module.importimport all.
The imported private scope includes private imports of the imported module, including nested Lean.Parser.Module.importimport alls.
As a consequence, the set of private scopes accessible to the current module is the transitive closure of Lean.Parser.Module.importimport all declarations.
The module system's Lean.Parser.Module.importimport all is more powerful than Lean.Parser.Module.importimport without the module system.
It makes imported private definitions accessible directly by name, as if they were defined in the current module.
A secondary use case for Lean.Parser.Module.importimport all is to access code in multiple modules within a library that should nonetheless not be provided to downstream consumers, as well as to allow tests to access information that is not part of the public API.
Importing Private Information
This library separates a module of definitions from a module of lemmas.
This is a common pattern in Lean code.
However, because Tree.count is not exposed, the proof in the lemma file cannot unfold it:
Tree/Lemmas.leanmodulepublicimportTree.BasictheoremTree.count_leaf_eq_zero:count(.leaf:Treeα)=0:=α:Type u_1⊢ leaf.count=0`simp` made no progressα:Type u_1⊢ leaf.count=0
Invalid simp theorem `count`: Expected a definition with an exposed body
Importing the private scope from Tree.Basic into the lemma module allows the definition to be unfolded in the proof.
Definitions in Lean result in both a representation in the type theory that is designed for formal reasoning and a compiled representation that is designed for execution.
This compiled representation is used to generate machine code, but it can also be executed directly using an interpreter.
The code that runs during elaboration, such as tactics or macros, is the compiled form of definitions.
If this compiled representation changes, then any code created by it may no longer be up to date, and it must be re-run.
Because the compiler performs non-trivial optimizations, changes to any definition in the transitive dependency chain of a function could in principle invalidate its compiled representation.
This means that metaprograms exported by modules induce a much stronger coupling than ordinary definitions.
Furthermore, metaprograms run during the construction of ordinary terms; thus, they must be fully defined and compiled before use.
After all, a function definition without a body cannot be run.
The time at which metaprograms are run is referred to as the metaprogramming phase, frequently just called the meta phase.
Just as they distinguish between public and private information, modules additionally distinguish code that is available in the meta phase from ordinary code.
Any declaration used as an entry point to compile-time execution has to be tagged with the Lean.Parser.Module.importmeta modifier, which indicates that the declaration is available for use as a metaprogram.
This is automatically done in built-in metaprogramming syntax such as Lean.Parser.Command.syntax : commandsyntax, Lean.Parser.Command.macro : commandmacro, and Lean.Parser.Command.elab : commandelab but may need to be done explicitly when manually applying metaprogramming attributes such as app_delab or when defining helper declarations.
A Parser.Command.declModifiersmeta definition may only access (and thus invoke) other Parser.Command.declModifiersmeta definitions in execution-relevant positions; a non-Parser.Command.declModifiersmeta definition likewise may only access other non-Parser.Command.declModifiersmeta definitions.
Meta Definitions
In this module, the helper function revArrays reverses the order of the elements in each array literal in a term.
This is called by the macro rev!.
Main.leanmoduleopenLeanvariable[Monadm][MonadRefm][MonadQuotationm]partialdefrevArrays:Syntax→mTerm|`(#[$xs,*])=>`(#[$((xs:ArrayTerm).reverse),*])|other=>domatchotherwith|.nodekiargs=>pure⟨.nodeki(←args.mapMrevArrays)⟩|_=>pure⟨other⟩Invalid `meta` definition `_aux___macroRules_termRev!__1`, `revArrays` not marked `meta`macro"rev!"e:term:term=>dorevArrayse
The error message indicates that revArrays cannot be used from the macro because it is not defined in the module's metaprogramming phase:
Invalid `meta` definition `_aux___macroRules_termRev!__1`, `revArrays` not marked `meta`
Libraries that were not originally part of the meta phase can be brought into it by importing a module with Parser.Module.importmeta import.
When a module is imported at the meta phase, all of its definitions are made available at that phase, whether or not they were marked Parser.Command.declModifiersmeta.
There is no meta-meta phase.
In addition to making the imported module's public contents available at the meta phase, Parser.Module.importmeta import indicates that the current module should be rebuilt if the compiled representation of the imported module changes, ensuring that modified metaprograms are re-run.
If a definition should be usable in both phases, then it must be defined in a separate module and imported at both phases.
Cross-Phase Code Reuse
In this module, the function toPalindrome is defined in the meta phase, which allows it to be used in a macro but not in an ordinary definition:
If the macro pal! were public (that is, if it was not declared with the local modifier) then the Lean.Parser.Module.importmeta import of Phases.Pal would need to be declared Lean.Parser.Module.importpublic as well.
In addition, the import must be public if the imported definition may be executed at compile time outside the current module, i.e. if it is reachable from some public Parser.Command.declModifiersmeta definition in the current module.
Use Parser.Module.importpublic meta import.
If the declaration is already declared Parser.Command.declModifiersmeta, then Parser.Module.importpublic import is sufficient.
Unlike definitions, most metaprograms are public by default.
Thus, most Lean.Parser.Module.importmeta import are also Parser.Module.importpublic in practice.
The exception is when a definition is imported solely for use in local metaprograms, such as those declared with Parser.Command.syntaxlocal syntax, Parser.Command.macrolocal macro, or Parser.Command.elablocal elab.
As a guideline, it is usually preferable to keep the amount of Lean.Parser.Command.declModifiers`declModifiers` 是声明修饰符的集合,包括:
* 文档注释 `/-- ... -/`
* 属性列表 `@[attr1, attr2]`
* 可见性说明符 `private` 或 `public`
* `protected`
* `noncomputable`
* `unsafe`
* `partial` 或 `nonrec`
所有修饰符都是可选的,并且必须按上述顺序出现。
`nestedDeclModifiers` 与 `declModifiers` 相同,但属性与声明打印在同一行;它用于嵌套在其他语法中的声明,例如结构体字段。meta annotations as small as possible.
This avoids locking otherwise-reusable declarations into the meta phase and it helps the build system avoid more rebuilds.
Thus, when a metaprogram depends on other code that does not itself need to be marked Lean.Parser.Command.declModifiers`declModifiers` 是声明修饰符的集合,包括:
* 文档注释 `/-- ... -/`
* 属性列表 `@[attr1, attr2]`
* 可见性说明符 `private` 或 `public`
* `protected`
* `noncomputable`
* `unsafe`
* `partial` 或 `nonrec`
所有修饰符都是可选的,并且必须按上述顺序出现。
`nestedDeclModifiers` 与 `declModifiers` 相同,但属性与声明打印在同一行;它用于嵌套在其他语法中的声明,例如结构体字段。meta, this other code should be placed in a separate module and not marked Lean.Parser.Command.declModifiers`declModifiers` 是声明修饰符的集合,包括:
* 文档注释 `/-- ... -/`
* 属性列表 `@[attr1, attr2]`
* 可见性说明符 `private` 或 `public`
* `protected`
* `noncomputable`
* `unsafe`
* `partial` 或 `nonrec`
所有修饰符都是可选的,并且必须按上述顺序出现。
`nestedDeclModifiers` 与 `declModifiers` 相同,但属性与声明打印在同一行;它用于嵌套在其他语法中的声明,例如结构体字段。meta.
Only the final module that actually registers a metaprogram needs the helpers to be in the meta phase.
This module should use Lean.Parser.Module.importpublic meta import to import those helpers and then define its metaprograms using built-in syntax like Parser.Command.elabelab, using Lean.Parser.Command.declaration : commandmeta def, or using Lean.Parser.Command.section : commandA `section`/`end` pair delimits the scope of `variable`, `include`, `open`, `set_option`, and `local`
commands. Sections can be nested. `section <id>` provides a label to the section that has to appear
with the matching `end`. In either case, the `end` can be omitted, in which case the section is
closed at the end of the file.
meta section.
The following list contains common errors one might encounter when using the module system and especially porting existing files to the module system:
Unknown constant errors
Check whether a private definition is being accessed in the public scope.
If so, the problem can be solved by making the current declaration private as well, or by placing the reference into the private scope using the Lean.Parser.Term.structInstFieldDef : structInstFieldDeclprivate modifier on a field or Lean.Parser.Term.byby for a proof.
Definitional equality errors, especially after porting
Failures of expected definitional equalities are usually due to a missing expose attribute on a definition or alternatively, if imported, an Lean.Parser.Module.importimport all.
Prefer the former if anyone outside your library might feasibly require the same access.
The error message should list non-exposed definitions that could not be unfolded.
This may also appear as a kernel error when a tactic directly emits proof terms that reference specific declarations without going through the elaborator, such as for proof by reflection.
In this case, there is no readily available trace for debugging; consider using @[expose]Parser.Command.sectionsections generously on the closure of relevant modules.
To gain the benefits of the module system, source files must be made into modules.
Start by enabling the module system throughout all files with minimal breaking changes:
Prefix all files with Lean.Parser.Module.headermodule.
Make all existing imports Lean.Parser.Command.declModifiers`declModifiers` 是声明修饰符的集合,包括:
* 文档注释 `/-- ... -/`
* 属性列表 `@[attr1, attr2]`
* 可见性说明符 `private` 或 `public`
* `protected`
* `noncomputable`
* `unsafe`
* `partial` 或 `nonrec`
所有修饰符都是可选的,并且必须按上述顺序出现。
`nestedDeclModifiers` 与 `declModifiers` 相同,但属性与声明打印在同一行;它用于嵌套在其他语法中的声明,例如结构体字段。public unless they will be used only in proofs.
Add Lean.Parser.Module.importimport all when errors that mention references to private data occur.
Add Lean.Parser.Module.importpublic meta import when errors that mention “must be Lean.Parser.Module.importmeta” occur.
The Lean.Parser.Module.importpublic may be omitted when defining local-only metaprograms.
Prefix the remainder of the file with @[expose] public section or, for programming-focused files, with Lean.Parser.Command.section : commandA `section`/`end` pair delimits the scope of `variable`, `include`, `open`, `set_option`, and `local`
commands. Sections can be nested. `section <id>` provides a label to the section that has to appear
with the matching `end`. In either case, the `end` can be omitted, in which case the section is
closed at the end of the file.
public section.
The latter should be used for programs that will be run but not reasoned about.
After an initial build under the module system succeeds, the dependencies between modules can be iteratively minimized.
In particular, removing uses of Lean.Parser.Command.declModifiers`declModifiers` 是声明修饰符的集合,包括:
* 文档注释 `/-- ... -/`
* 属性列表 `@[attr1, attr2]`
* 可见性说明符 `private` 或 `public`
* `protected`
* `noncomputable`
* `unsafe`
* `partial` 或 `nonrec`
所有修饰符都是可选的,并且必须按上述顺序出现。
`nestedDeclModifiers` 与 `declModifiers` 相同,但属性与声明打印在同一行;它用于嵌套在其他语法中的声明,例如结构体字段。public and @[expose] will help avoid unnecessary rebuilds.